/*
author:杨文超
email:yangwenchao@keynection.cn
*/
#include <stdio.h>
#include <string.h>
#include <sys/unistd.h>
#include <sys/stat.h>
#include "esp_err.h"
#include "esp_log.h"
#include "geminai_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/i2s_std.h"
#include "driver/gpio.h"
#include "esp_check.h"
#include "geminai_audio.h"

static const char *TAG = "WAVE_PLAYER";

typedef struct {
    // The "RIFF" chunk descriptor
    uint8_t ChunkID[4];
    int32_t ChunkSize;
    uint8_t Format[4];
    // The "fmt" sub-chunk
    uint8_t Subchunk1ID[4];
    int32_t Subchunk1Size;
    int16_t AudioFormat;
    int16_t NumChannels;
    int32_t SampleRate;
    int32_t ByteRate;
    int16_t BlockAlign;
    int16_t BitsPerSample;
    // The "data" sub-chunk
    uint8_t Subchunk2ID[4];
    int32_t Subchunk2Size;
} wav_header_t;



#define GEMINAI_I2S_STD_BCLK_IO        GPIO_NUM_45      // I2S bit clock io number
#define GEMINAI_I2S_STD_WS_IO          GPIO_NUM_46      // I2S word select io number
#define GEMINAI_I2S_STD_DOUT_IO        GPIO_NUM_42      // I2S data out io number
#define GEMINAI_I2S_STD_DIN_IO         GPIO_NUM_NC      // I2S data in io number
#define TEST_TAG "audio_test_log"

static i2s_chan_handle_t                i2s_tx_chan;        // I2S controller tx channel handler
TaskHandle_t xAudioTaskHandle = NULL;  // 检测队列栈空间
void geminai_i2s_init_std_simplex(void)
{
    /* Setp 1: Determine the I2S channel configuration and allocate two channels one by one
     * The default configuration can be generated by the helper macro,
     * it only requires the I2S controller id and I2S role
     * The tx and rx channels here are registered on different I2S controller,
     * only ESP32-C3, ESP32-S3 and ESP32-H2 allow to register two separate tx & rx channels on a same controller */
    i2s_chan_config_t i2s_tx_chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER);
    ESP_ERROR_CHECK(i2s_new_channel(&i2s_tx_chan_cfg, &i2s_tx_chan, NULL));

    /* Step 2: Setting the configurations of standard mode and initialize each channels one by one
     * The slot configuration and clock configuration can be generated by the macros
     * These two helper macros is defined in 'i2s_std.h' which can only be used in STD mode.
     * They can help to specify the slot and clock configurations for initialization or re-configuring */
    i2s_std_config_t i2s_tx_std_cfg = {
        .clk_cfg  = I2S_STD_CLK_DEFAULT_CONFIG(8000),
        .slot_cfg = I2S_STD_PCM_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO),
        .gpio_cfg = {
            .mclk = I2S_GPIO_UNUSED,    // some codecs may require mclk signal, this example doesn't need it
            .bclk = GEMINAI_I2S_STD_BCLK_IO,
            .ws   = GEMINAI_I2S_STD_WS_IO,
            .dout = GEMINAI_I2S_STD_DOUT_IO,
            .din  = GEMINAI_I2S_STD_DIN_IO,
            .invert_flags = {
                .mclk_inv = false,
                .bclk_inv = false,
                .ws_inv   = false,
            },
        },
    };

    ESP_ERROR_CHECK(i2s_channel_init_std_mode(i2s_tx_chan, &i2s_tx_std_cfg));   
    ESP_ERROR_CHECK(i2s_channel_enable(i2s_tx_chan));

}



void play_wav(void *args)
{
    FILE *fd = NULL;
    struct stat file_stat;
    char *filepath=(char *)args;

    if (stat(filepath, &file_stat) == -1) {
        GEMINAI_LOGE(TAG, "Failed to stat file : %s", filepath);
        return;
    }

    GEMINAI_LOGI(TAG, "file stat info: %s (%ld bytes)...", filepath, file_stat.st_size);
    fd = fopen(filepath, "r");
    if (NULL == fd) {
        GEMINAI_LOGE(TAG, "Failed to read existing file : %s", filepath);
        return;
    }
    const size_t chunk_size = 1024;
    uint8_t *buffer = malloc(chunk_size);

    if (NULL == buffer) {
        GEMINAI_LOGE(TAG, "audio data buffer malloc failed");
        fclose(fd);
        return;
    }

    /**
     * read head of WAV file
     */
    wav_header_t wav_head;
    int len = 0 ;
    len = fread(&wav_head, 1, sizeof(wav_header_t), fd);
    if (len <= 0) {
        ESP_LOGE(TAG, "Read wav header failed");
        free(buffer);
        fclose(fd);
        return;
    }
    if (NULL == strstr((char *)wav_head.Subchunk1ID, "fmt") &&
            NULL == strstr((char *)wav_head.Subchunk2ID, "data")) {
        ESP_LOGE(TAG, "Header of wav format error");
        free(buffer);
        fclose(fd);
        return;
    }
    // GEMINAI_LOGI(TAG, "frame_rate= %"PRIi32", ch=%d, width=%d", wav_head.SampleRate, wav_head.NumChannels, wav_head.BitsPerSample);


    /**
     * read wave data of WAV file
     */
    size_t write_num = 0;
    // GEMINAI_LOGI(TEST_TAG,"begin read wav");
    do {
        /* Read file in chunks into the scratch buffer */
        len = fread(buffer, 1, chunk_size, fd);
        if (len <= 0) {
            break;
        }

        i2s_channel_write(i2s_tx_chan, buffer, len, NULL, 1000);  
        write_num += len;
    } while (1);
    // GEMINAI_LOGI(TEST_TAG,"finsh read wav");
    free(buffer);
    fclose(fd);
    GEMINAI_LOGI(TAG, "File reading complete, total: %d bytes", write_num);

}

// 定义队列
QueueHandle_t audio_queue;  // 音频播放队列
UBaseType_t audio_queue_size ; // 播放队列的空闲数量
/**
 * @brief  将结构体放入队列当中
 * 
 * @param type  代表播报的方式 循环 单次
 * @param filepath  文件存放的路径
 */
void app_play_wav(int play_count, char *filepath)
{
    audio_queue_size = uxQueueSpacesAvailable(audio_queue);  // 获取队列的空闲空间数量
    if (audio_queue_size == 0)
    {
        ESP_LOGW(TAG, "Audio queue size full");
        return;
    }

    if (filepath == NULL || *filepath == '\0')
    {
        ESP_LOGW(TAG, "Invalid path");
        return;
    }
    
    audio_message_t msg;
    msg.filepath = filepath;  // filepath是一个有效的字符串指针
    msg.play_count = play_count;

    if (xQueueSend(audio_queue, &msg, pdMS_TO_TICKS(10)) != pdPASS) {
        ESP_LOGE(TEST_TAG, "Failed to enqueue audio message");
    } else {
        ESP_LOGI(TEST_TAG, "Audio message enqueued: %s", filepath);
    }
}

// 音频播放任务，用于从队列中读取文件路径并播放
void audio_task(void *pvParameters)
{
    audio_message_t message;
    
    while (1) {
        // 从队列中接收结构体消息
        if (xQueueReceive(audio_queue, &message, pdMS_TO_TICKS(50)) == pdPASS) {
            if (message.filepath == NULL || *message.filepath == '\0') {
                ESP_LOGE(TEST_TAG, "Received invalid file path: %s", message.filepath ? message.filepath : "NULL");
            } else {
                if (message.play_count > 0) {
                    // 如果是只执行一次的消息，直接播放
                    ESP_LOGD(TEST_TAG, " message, turn on");
                    play_wav(message.filepath);  // 调用 play_wav 播放音频
                    ESP_LOGD(TEST_TAG, "message, turn off");

                    // 将消息放回队列末尾，继续处理
                    message.play_count--;
                    if (message.play_count > 0) {
                        if (xQueueSend(audio_queue, &message, 0) != pdPASS) {
                            ESP_LOGE(TEST_TAG, "Failed to requeue loop message");
                        }
                    }
                }
            }
        }

        // 延迟 200ms，避免 CPU 占用过高
        vTaskDelay(pdMS_TO_TICKS(500));
    }
}
/**
 * @brief 用于创建监测语音播放队列
 * 
 */
void audio_create_task()
{
    // 创建消息队列，队列长度为 15
    audio_queue = xQueueCreate(15, sizeof(audio_message_t));
    if (audio_queue == NULL) {
        ESP_LOGE(TAG, "Failed to create audio queue!");
        return;
    }
    ESP_LOGI(TEST_TAG,"begin create task");
    // 创建音频播放任务，任务会从队列中读取并处理播放请求
    xTaskCreate(audio_task, "audio_task", 3072, NULL, 12, &xAudioTaskHandle);    
    ESP_LOGI(TEST_TAG,"finsh create task");
}




void test_demo_audio()
{
    audio_create_task();

    ESP_LOGI(TEST_TAG,"begin one time battery aduio");
    app_play_wav(ONE_TIME_MESSAGE,BATTERY_ERROR_AUDIO);
    ESP_LOGI(TEST_TAG,"finsh one time battery aduio");

    ESP_LOGI(TEST_TAG,"begin loop comp aduio");
    app_play_wav(LOOP_MESSAGE,COMP_ERROR_AUDIO);
    ESP_LOGI(TEST_TAG,"finsh loop comp aduio");

    ESP_LOGI(TEST_TAG,"begin loop battery aduio");
    app_play_wav(LOOP_MESSAGE,BATTERY_ERROR_AUDIO);
    ESP_LOGI(TEST_TAG,"finsh loop battery aduio");
}